/** @module GUI */

import Controller from './Controller';
import BooleanController from './BooleanController';
import ColorController from './ColorController';
import FunctionController from './FunctionController';
import NumberController from './NumberController';
import OptionController from './OptionController';
import StringController from './StringController';

import stylesheet from 'stylesheet';
import _injectStyles from './utils/injectStyles';
import ObjectPicController from './ObjectPicController';
import VectorController from './VectorController';
import TextAreaController from './TextAreaController';
import BooleanMoreController from './BooleanMoreController';
import LabelController from './LabelController' ;
import RadioController from './RadioController';
import HtmlController from './HtmlController';
import GUIFieldset from './GUIFieldset';
import EditorController from './EditorController';
import OptionEditController from './OptionEditController.js';

let stylesInjected = false;

export default class GUI {

	/**
	 * Creates a panel that holds controllers.
	 * @example
	 * new GUI();
	 * new GUI( { container: document.getElementById( 'custom' ) } );
	 *
	 * @param {object} [options]
	 * @param {boolean} [options.autoPlace=true]
	 * Adds the GUI to `document.body` and fixes it to the top right of the page.
	 *
	 * @param {HTMLElement} [options.container]
	 * Adds the GUI to this DOM element. Overrides `autoPlace`.
	 *
	 * @param {number} [options.width=245]
	 * Width of the GUI in pixels, usually set when name labels become too long. Note that you can make
	 * name labels wider in CSS with `.lil‑gui { ‑‑name‑width: 55% }`
	 *
	 * @param {string} [options.title=Controls]
	 * Name to display in the title bar.
	 *
	 * @param {boolean} [options.closeFolders=false]
	 * Pass `true` to close all folders in this GUI by default.
	 *
	 * @param {boolean} [options.injectStyles=true]
	 * Injects the default stylesheet into the page if this is the first GUI.
	 * Pass `false` to use your own stylesheet.
	 *
	 * @param {number} [options.touchStyles=true]
	 * Makes controllers larger on touch devices. Pass `false` to disable touch styles.
	 *
	 * @param {GUI} [options.parent]
	 * Adds this GUI as a child in another GUI. Usually this is done for you by `addFolder()`.
	 *
	 */
	constructor( {
		parent,
		autoPlace = parent === undefined,
		container,
		width,
		title = 'Controls',
		titleClass,
		closeFolders = false,
		injectStyles = true,
		touchStyles = true,
		horizontal = false,
		rootHideTitle = false
	} = {} ) {

		/**
		 * The GUI containing this folder, or `undefined` if this is the root GUI.
		 * @type {GUI}
		 */
		this.parent = parent;

		this.group = null ;
		this.isFolder = true ;
		this._onClick = null;
		//是否顶级隐藏标题
		this.rootHideTitle = rootHideTitle ;

		if(horizontal === true) closeFolders = true ;

		/**
		 * The top level GUI containing this folder, or `this` if this is the root GUI.
		 * @type {GUI}
		 */
		this.root = parent ? parent.root : this;

		/**
		 * The list of controllers and folders contained by this GUI.
		 * @type {Array<GUI|Controller>}
		 */
		this.children = [];

		/**
		 * The list of controllers contained by this GUI.
		 * @type {Array<Controller>}
		 */
		this.controllers = [];

		/**
		 * The list of folders contained by this GUI.
		 * @type {Array<GUI>}
		 */
		this.folders = [];

		this.fieldsets = [] ;

		/**
		 * Used to determine if the GUI is closed. Use `gui.open()` or `gui.close()` to change this.
		 * @type {boolean}
		 */
		this._closed = false;

		/**
		 * Used to determine if the GUI is hidden. Use `gui.show()` or `gui.hide()` to change this.
		 * @type {boolean}
		 */
		this._hidden = false;

		this.horizontal = horizontal ;

		// GUI.nextId = GUI.nextId || 0 ;
		// this.domId = `lil-gui-${++GUI.nextId}` ;

		/**
		 * The outermost container element.
		 * @type {HTMLElement}
		 */
		this.domElement = document.createElement( 'div' );
		// this.domElement.setAttribute("id",this.domId + "-content") ;
		this.domElement.classList.add( 'lil-gui' );

		/**
		 * The DOM element that contains the title.
		 * @type {HTMLElement}
		 */
		this.$title = document.createElement( 'div' );
		// this.$title.setAttribute("id",this.domId + "-title") ;
		this.$title.classList.add( 'title' );
		if(titleClass != null && titleClass != "")
			this.$title.classList.add( titleClass );
		if(this.rootHideTitle === true && this.root == this)
			this.$title.setAttribute("style","display:none") ;
		this.$title.setAttribute( 'role', 'button' );
		this.$title.setAttribute( 'aria-expanded', true );
		this.$title.setAttribute( 'tabindex', 0 );

		this.$title.addEventListener( 'click', () => this.openAnimated( this._closed ) );
		this.$title.addEventListener( 'keydown', e => {
			if ( e.code === 'Enter' || e.code === 'Space' ) {
				e.preventDefault();
				this.$title.click();
			}
		} );

		// enables :active pseudo class on mobile
		this.$title.addEventListener( 'touchstart', () => {}, { passive: true } );

		/**
		 * The DOM element that contains children.
		 * @type {HTMLElement}
		 */
		this.$children = document.createElement( 'div' );
		this.$children.classList.add( 'children' );

		this.$childrenTitle = document.createElement("div") ;
		this.$childrenTitle.classList.add( 'childrenTitle' ) ;

		this.hasAppendChildrenTitle = false ;

		// this.$children.appendChild( this.$childrenTitle) ;
		if(this.parent != null && this.parent.horizontal === true){
			if(this.parent.hasAppendChildrenTitle == false){
				this.parent.$children.appendChild( this.parent.$childrenTitle) ;
				this.parent.hasAppendChildrenTitle = true ;
			}
			this.$title.classList.remove( 'title' );
			this.$title.classList.add( 'lilHorizontalTitle' );
			this.parent.$childrenTitle.appendChild( this.$title );
		}else
			this.domElement.appendChild( this.$title );
		this.domElement.appendChild( this.$children );

		this.title( title );
		let initHeight = 0 ;
		if ( this.parent ) {

			this.parent.children.push( this );
			this.parent.folders.push( this );

			if(window.d3 != null){
				this.parent.$children.classList.add( 'selModel');
				this.parent.$children.appendChild( this.domElement ).classList.add( 'allow-touch-styles',"closeNow" );
				this.parent.$children.children[0].classList.remove("closeNow" );
			}else
				this.parent.$children.appendChild( this.domElement );

			// Stop the constructor early, everything onward only applies to root GUI's
			return;

		}

		this.domElement.classList.add( 'root' );

		if ( touchStyles ) {
			this.domElement.classList.add( 'allow-touch-styles' );
		}

		// Inject stylesheet if we haven't done that yet
		if ( !stylesInjected && injectStyles ) {
			_injectStyles( stylesheet );
			stylesInjected = true;
		}

		if ( container ) {

			container.appendChild( this.domElement );

		} else if ( autoPlace ) {

			this.domElement.classList.add( 'autoPlace' );
			document.body.appendChild( this.domElement );

		}

		if ( width ) {
			this.domElement.style.setProperty( '--width', width + 'px' );
		}

		this._closeFolders = closeFolders;

	}

	addFieldset(legend,parent=null){
		this.$fieldset = new GUIFieldset(this,legend,parent) ;
		this.fieldsets.push(this.$fieldset) ;
		return this.$fieldset ;
	}

	/**
	 * 设置当前fieldset，主要是动态添加行的地方使用
	 * @param fieldset
	 * @returns {GUI}
	 */
	setFieldset(fieldset) {
		if(fieldset == null) return this ;
		this.$fieldset = fieldset ;
		return this ;
	}

	clearFieldset(){
		this.$fieldset = null ;
	}

	getControllerContainer(){
		if(this.$fieldset != null) return this.$fieldset.getControllerContainer() ;
		return this.$children ;
	}

	/**
	 * Adds a controller to the GUI, inferring controller type using the `typeof` operator.
	 * @example
	 * gui.add( object, 'property' );
	 * gui.add( object, 'number', 0, 100, 1 );
	 * gui.add( object, 'options', [ 1, 2, 3 ] );
	 *
	 * @param {object} object The object the controller will modify.
	 * @param {string} property Name of the property to control.
	 * @param {number|object|Array} [$1] Minimum value for number controllers, or the set of
	 * selectable values for a dropdown.
	 * @param {number} [max] Maximum value for number controllers.
	 * @param {number} [step] Step value for number controllers.
	 * @returns {Controller}
	 */
	add( object, property, $1, max, step ) {

		if ( Object( $1 ) === $1 ) {

			return new OptionController( this, object, property, $1 );

		}

		const initialValue = object[ property ];

		switch ( typeof initialValue ) {

			case 'number':

				return new NumberController( this, object, property, $1, max, step );

			case 'boolean':

				return new BooleanController( this, object, property );

			case 'string':

				return new StringController( this, object, property );

			case 'function':

				return new FunctionController( this, object, property );
			case 'object':
				if(object[property].controllerType === 1)
					return new ObjectPicController(this, object, property );

		}
		// eslint-disable-next-line no-console
		console.error( `gui.add failed
	property:`, property, `
	object:`, object, `
	value:`, initialValue );

	}

	addButton(func,config){
		let param = {"func":func} ;
		return new FunctionController( this, param, "func",config );
	}

	addNumber( object, property, $1, max, step,config) {
		return new NumberController( this, object, property, $1, max, step,config);
	}

	addSelect(object, property, options,config){
		if(config != null && config.editable === true)
			return new OptionEditController(this,object, property, options,config);
		return new OptionController( this, object, property, options,config);
	}

	addRadio(object, property, options,config){
		return new RadioController( this, object, property, options,config);
	}

	addVector(object, property,subKeys,min, max, step){
		return new VectorController(this, object, property,subKeys,min, max, step);
	}

	addBoolean(object, property,subKeys,config){
		return new BooleanMoreController(this, object, property,subKeys,config);
	}

	addCheckbox(object, property,config){
		return new BooleanController( this, object, property,config);
	}

	addTextArea(object, property,config){
		return new TextAreaController( this, object, property, config );
	}

	addLabel(object, property,config){
		return new LabelController(this,object,property,config) ;
	}

	addInput(object, property,config){
		return new StringController(this, object, property,config) ;
	}

	addHtml(html,config){
		let param = {"html":html} ;
		return new HtmlController(this, param, "html",config) ;
	}

	addEditor(object, property,config){
		return new EditorController(this, object, property,config) ;
	}

	/**
	 * 添加图标
	 * @param ico 图标样式
	 * @param config
	 * @param func
	 */
	addIcon(ico,config,func){
		if(config == null) config = {} ;
		let title = config.title ;
		if(title == null) title = "" ;
		let uuid = this.getUUID() ;
		let html = `<i class="${ico}" title="${title}" id="${uuid}"></i>` ;
		let controller =  this.addHtml(html,config).widthUnset().hideName() ;
		if(func != null){
			controller.domElement.addEventListener("click",function(event){
				if(controller._disabled === true) return ;
				if(event.target != null && event.target.id === uuid)
					func(event,controller) ;
			}) ;
		}
		return controller ;
	}

	/**
	 * 添加超链接
	 * @param config
	 * @param func
	 * @returns {*}
	 */
	addLink(name,config,func){
		if(config == null) config = {} ;
		let title = config.title ;
		if(title == null) title = "" ;
		if(config.clazz == null) config.clazz = this.root.linkClass ;
		let uuid = this.getUUID() ;
		let html = `<a class="${config.clazz}" title="${title}" id="${uuid}">${name}</a>` ;
		let controller =  this.addHtml(html,config).widthUnset().hideName().setNowrap() ;
		if(func != null){
			controller.domElement.addEventListener("click",function(event){
				if(controller._disabled === true) return ;
				if(event.target != null && event.target.id === uuid)
					func(event,controller) ;
			}) ;
		}
		return controller ;
	}

	/**
	 * Adds a color controller to the GUI.
	 * @example
	 * params = {
	 * 	cssColor: '#ff00ff',
	 * 	rgbColor: { r: 0, g: 0.2, b: 0.4 },
	 * 	customRange: [ 0, 127, 255 ],
	 * };
	 *
	 * gui.addColor( params, 'cssColor' );
	 * gui.addColor( params, 'rgbColor' );
	 * gui.addColor( params, 'customRange', 255 );
	 *
	 * @param {object} object The object the controller will modify.
	 * @param {string} property Name of the property to control.
	 * @param {number} rgbScale Maximum value for a color channel when using an RGB color. You may
	 * need to set this to 255 if your colors are too bright.
	 * @returns {Controller}
	 */
	addColor( object, property, rgbScale = 1,config) {
		return new ColorController( this, object, property, rgbScale,config);
	}

	/**
	 * Adds a folder to the GUI, which is just another GUI. This method returns
	 * the nested GUI so you can add controllers to it.
	 * @example
	 * const folder = gui.addFolder( 'Position' );
	 * folder.add( position, 'x' );
	 * folder.add( position, 'y' );
	 * folder.add( position, 'z' );
	 *
	 * @param {string} title Name to display in the folder's title bar.
	 * @returns {GUI}
	 */
	addFolder( title,config) {
		if(config == null) config = {} ;
		config.parent = this ;
		config.title = title ;
		const folder = new GUI( config );
		if(this.horizontal === true) {
			folder.close();
		}
		else if ( this.root._closeFolders ) folder.close();
		return folder;
	}

	/**
	 * Recalls values that were saved with `gui.save()`.
	 * @param {object} obj
	 * @param {boolean} recursive Pass false to exclude folders descending from this GUI.
	 * @returns {this}
	 */
	load( obj, recursive = true ) {

		if ( obj.controllers ) {

			this.controllers.forEach( c => {

				if ( c instanceof FunctionController ) return;

				if ( c._name in obj.controllers ) {
					c.load( obj.controllers[ c._name ] );
				}

			} );

		}

		if ( recursive && obj.folders ) {

			this.folders.forEach( f => {

				if ( f._title in obj.folders ) {
					f.load( obj.folders[ f._title ] );
				}

			} );

		}

		return this;

	}

	/**
	 * Returns an object mapping controller names to values. The object can be passed to `gui.load()` to
	 * recall these values.
	 * @example
	 * {
	 * 	controllers: {
	 * 		prop1: 1,
	 * 		prop2: 'value',
	 * 		...
	 * 	},
	 * 	folders: {
	 * 		folderName1: { controllers, folders },
	 * 		folderName2: { controllers, folders }
	 * 		...
	 * 	}
	 * }
	 *
	 * @param {boolean} recursive Pass false to exclude folders descending from this GUI.
	 * @returns {object}
	 */
	save( recursive = true ) {

		const obj = {
			controllers: {},
			folders: {}
		};

		this.controllers.forEach( c => {

			if ( c instanceof FunctionController ) return;

			if ( c._name in obj.controllers ) {
				throw new Error( `Cannot save GUI with duplicate property "${c._name}"` );
			}

			obj.controllers[ c._name ] = c.save();

		} );

		if ( recursive ) {

			this.folders.forEach( f => {

				if ( f._title in obj.folders ) {
					throw new Error( `Cannot save GUI with duplicate folder "${f._title}"` );
				}

				obj.folders[ f._title ] = f.save();

			} );

		}

		return obj;

	}

	/**
	 * Opens a GUI or folder. GUI and folders are open by default.
	 * @param {boolean} open Pass false to close
	 * @returns {this}
	 * @example
	 * gui.open(); // open
	 * gui.open( false ); // close
	 * gui.open( gui._closed ); // toggle
	 */
	open( open = true ) {
		//水平布局时，直接调用点击打开的方法，因为这里需要关闭其他单元格
		if(open === true && this.parent != null && this.parent.horizontal === true){
			this.openAnimated( open );
			return this ;
		}
		this._setClosed( !open );

		this.$title.setAttribute( 'aria-expanded', !this._closed );
		this.domElement.classList.toggle( 'closed', this._closed );

		this.triggerClick() ;

		return this ;
	}

	/**
	 * 触发点击事件，如果当前没有绑定，则会触发父级
	 * @returns {GUI}
	 */
	triggerClick(){
		if(this._onClick != null) {
			this._onClick( this,!this._closed );
			return this ;
		}
		if(this.root._onClick != null){
			this.root._onClick( this,!this._closed );
			return this ;
		}
		return this ;
	}

	/**
	 * 展开第一个文件夹
	 * @returns {GUI}
	 */
	openFirstFolder(){
		if(this.folders.length == 0) return this ;
		for(let i=0; i<this.folders.length; i++){
			let f = this.folders[i] ;
			if(f._hidden  == true) continue ;
			f.open(true) ;
			return this ;
		}
		return this ;
	}

	preFolder(){
		if(this.parent == null) return null ;
		if(this.parent.folders.length == 0) return null ;
		for(let i=1; i<this.parent.folders.length; i++){
			let f = this.parent.folders[i] ;
			if(f == this) return this.parent.folders[i-1] ;
		}
		return null ;
	}

	nextFolder(){
		if(this.parent == null) return null ;
		if(this.parent.folders.length == 0) return null ;
		for(let i=0; i<this.parent.folders.length-1; i++){
			let f = this.parent.folders[i] ;
			if(f == this) return this.parent.folders[i+1] ;
		}
		return null ;
	}

	/**
	 * Closes the GUI.
	 * @returns {this}
	 */
	close() {
		return this.open( false );
	}

	_setClosed( closed ) {
		if ( this._closed === closed ) return;
		this._closed = closed;
		this._callOnOpenClose( this );
	}

	/**
	 * Shows the GUI after it's been hidden.
	 * @param {boolean} show
	 * @returns {this}
	 * @example
	 * gui.show();
	 * gui.show( false ); // hide
	 * gui.show( gui._hidden ); // toggle
	 */
	show( show = true ) {

		this._hidden = !show;

		this.domElement.style.display = this._hidden ? 'none' : '';
		//水平显示的，标题不在domElement标签内，需要单独控制隐藏或显示
		if(this.parent != null && this.parent.horizontal == true)
			this.$title.style.display = this._hidden ? 'none' : '';

		this._trigerRefreshFolder() ;

		return this;

	}

	/**
	 * Hides the GUI.
	 * @returns {this}
	 */
	hide() {
		return this.show( false );
	}

	openAnimated( open = true ) {
		if(this.parent != null && this.parent.horizontal === true){
			//点击强制打开
			open = true ;
			for(let i=0; i<this.parent.folders.length; i++){
				let f = this.parent.folders[i] ;
				if(f == this) {
					f.domElement.classList.toggle( 'closed', !open );
					// f.domElement.classList.remove("closed") ;
					f.$title.classList.toggle('horizontalSelected',open) ;
				}else {
					// f.domElement.classList.add( 'closed' );
					f.domElement.classList.toggle( 'closed', open );
					f.$title.classList.toggle('horizontalSelected',!open) ;
				}
			}
		}

		// set state immediately
		this._setClosed( !open );

		this.$title.setAttribute( 'aria-expanded', !this._closed );
		this.triggerClick() ;
		this._trigerRefreshFolder() ;

		if(this.parent != null && this.parent.horizontal === true){
			return ;
		}

		// wait for next frame to measure $children
		requestAnimationFrame( () => {
			// explicitly set initial height for transition
			if(window.d3 != null){
				for(let len = 0;len < this.domElement.parentElement.children.length;len++){
					this.domElement.parentElement.children[len].classList.add("closeNow")
				}
				this.domElement.classList.remove("closed","closeNow", !open), requestAnimationFrame(() => {
					// this.$children.style.height = s + "px"
				})
			}else{
				const initialHeight = this.$children.clientHeight;
				this.$children.style.height = initialHeight + 'px';

				this.domElement.classList.add( 'transition' );

				const onTransitionEnd = e => {
					if ( e.target !== this.$children ) return;
					this.$children.style.height = '';
					this.domElement.classList.remove( 'transition' );
					this.$children.removeEventListener( 'transitionend', onTransitionEnd );
				};

				this.$children.addEventListener( 'transitionend', onTransitionEnd );

				// todo: this is wrong if children's scrollHeight makes for a gui taller than maxHeight
				const targetHeight = !open ? 0 : this.$children.scrollHeight;

				this.domElement.classList.toggle( 'closed', !open );

				requestAnimationFrame( () => {
					this.$children.style.height = targetHeight + 'px';
				} );
			}
		} );

		return this;

	}

	/**
	 * Change the title of this GUI.
	 * @param {string} title
	 * @returns {this}
	 */
	title( title ) {
		/**
		 * Current title of the GUI. Use `gui.title( 'Title' )` to modify this value.
		 * @type {string}
		 */
		this._title = title;
		this.$title.innerHTML = title;
		this.$title.setAttribute( 'title', title );
		return this;
	}

	/**
	 * Resets all controllers to their initial values.
	 * @param {boolean} recursive Pass false to exclude folders descending from this GUI.
	 * @returns {this}
	 */
	reset( recursive = true ) {
		const controllers = recursive ? this.controllersRecursive() : this.controllers;
		controllers.forEach( c => c.reset() );
		return this;
	}

	/**
	 * Pass a function to be called whenever a controller in this GUI changes.
	 * @param {function({object:object, property:string, value:any, controller:Controller})} callback
	 * @returns {this}
	 * @example
	 * gui.onChange( event => {
	 * 	event.object     // object that was modified
	 * 	event.property   // string, name of property
	 * 	event.value      // new value of controller
	 * 	event.controller // controller that was modified
	 * } );
	 */
	onChange( callback ) {
		/**
		 * Used to access the function bound to `onChange` events. Don't modify this value
		 * directly. Use the `gui.onChange( callback )` method instead.
		 * @type {Function}
		 */
		this._onChange = callback;
		return this;
	}

	_callOnChange( controller ) {
		if(controller.constructor === FunctionController)
			return ;
		if ( this.parent ) {
			this.parent._callOnChange( controller );
		}

		let eventController = controller.$eventController ;
		if(eventController == null) eventController = controller ;
		if ( this._onChange !== undefined ) {
			this._onChange.call( this, {
				object: controller.object,
				property: controller.property,
				value: controller.getValue(),
				controller: eventController
			} );
		}
	}

	/**
	 * 点击事件
	 * @param callback
	 */
	onClick(callback){
		this._onClick = callback ;
		return this ;
	}

	/**
	 * Pass a function to be called whenever a controller in this GUI has finished changing.
	 * @param {function({object:object, property:string, value:any, controller:Controller})} callback
	 * @returns {this}
	 * @example
	 * gui.onFinishChange( event => {
	 * 	event.object     // object that was modified
	 * 	event.property   // string, name of property
	 * 	event.value      // new value of controller
	 * 	event.controller // controller that was modified
	 * } );
	 */
	onFinishChange( callback ) {
		/**
		 * Used to access the function bound to `onFinishChange` events. Don't modify this value
		 * directly. Use the `gui.onFinishChange( callback )` method instead.
		 * @type {Function}
		 */
		this._onFinishChange = callback;
		return this;
	}

	onFinishChangeFirst( callback ) {
		/**
		 * Used to access the function bound to `onFinishChange` events. Don't modify this value
		 * directly. Use the `gui.onFinishChange( callback )` method instead.
		 * @type {Function}
		 */
		this._onFinishChangeFirst = callback;
		return this;
	}

	_callOnFinishChange( controller ) {

		if ( this.parent ) {
			this.parent._callOnFinishChange( controller );
		}

		if ( this._onFinishChangeFirst !== undefined ) {
			this._onFinishChangeFirst.call( this, {
				object: controller.object,
				property: controller.property,
				value: controller.getValue(),
				controller
			} );
		}

		if ( this._onFinishChange !== undefined ) {
			this._onFinishChange.call( this, {
				object: controller.object,
				property: controller.property,
				value: controller.getValue(),
				controller
			} );
		}
	}

	/**
	 * Pass a function to be called when this GUI or its descendants are opened or closed.
	 * @param {function(GUI)} callback
	 * @returns {this}
	 * @example
	 * gui.onOpenClose( changedGUI => {
	 * 	console.log( changedGUI._closed );
	 * } );
	 */
	onOpenClose( callback ) {
		this._onOpenClose = callback;
		return this;
	}

	_callOnOpenClose( changedGUI ) {
		if ( this.parent ) {
			this.parent._callOnOpenClose( changedGUI );
		}

		if ( this._onOpenClose !== undefined ) {
			this._onOpenClose.call( this, changedGUI );
		}
	}

	/**
	 * 更新文件夹显示，目前只针对水平布局时，如果下级全部隐藏时，则对头部的做隐藏
	 */
	refreshFolder(){
		if(this.folders.length == 0) return this;
		if(this.horizontal === true && this.hasAppendChildrenTitle == true){
			let allHide = true ;
			for(let i=0; i<this.folders.length; i++){
				if(this.folders[i]._hidden === false){
					allHide = false ;
					break ;
				}
			}
			if(allHide === true){
				this.$childrenTitle.style.display = "none" ;
			}else{
				this.$childrenTitle.style.display = "" ;
			}
		}

		return this ;
	}

	_trigerRefreshFolder(){
		if(this.parent == null) return ;
		this.parent.refreshFolder() ;
	}

	/**
	 * Destroys all DOM elements and event listeners associated with this GUI
	 */
	destroy() {
		try {
			if (this.parent) {
				if (this.parent.horizontal === true && this.parent.hasAppendChildrenTitle == true) {
					this.parent.$childrenTitle.removeChild( this.$title );
					//如果当前是打开状态，关闭后打开临近选项卡
					if (this._hidden === false) {
						let openFolder = this.nextFolder();
						if (openFolder == null) openFolder = this.preFolder();
						if (openFolder == null) this.parent.openFirstFolder();
						else openFolder.open();
					}
				}
				this.parent.children.splice( this.parent.children.indexOf( this ), 1 );
				this.parent.folders.splice( this.parent.folders.indexOf( this ), 1 );
			}

			if (this.domElement.parentElement) {
				this.domElement.parentElement.removeChild( this.domElement );
			}

			Array.from( this.children ).forEach( c => c.destroy() );

			if (this.group != null)
				this.group.removeFolder( this );

			Array.from( this.fieldsets ).forEach( c => c.destroy() );
		}catch (e){}
	}

	/**
	 * Returns an array of controllers contained by this GUI and its descendents.
	 * @returns {Controller[]}
	 */
	controllersRecursive() {
		let controllers = Array.from( this.controllers );
		this.folders.forEach( f => {
			controllers = controllers.concat( f.controllersRecursive() );
		} );
		return controllers;
	}

	updateDisplay(){
		this.refreshControllers() ;
	}

	refreshControllers(){
		let array = this.controllersRecursive() ;
		if(array == null || array.length == 0) return ;
		let len = array.length ;
		for(let i=0; i<len; i++){
			let c = array[i] ;
			c.updateDisplay() ;
		}
	}

	/**
	 * Returns an array of folders contained by this GUI and its descendents.
	 * @returns {GUI[]}
	 */
	foldersRecursive() {
		let folders = Array.from( this.folders );
		this.folders.forEach( f => {
			folders = folders.concat( f.foldersRecursive() );
		} );
		return folders;
	}

	addControllerClass(clazz){
		if(clazz == null || clazz == "") return this ;
		this.controllerClass = clazz ;
		return this ;
	}

	addControllerStyle(style){
		this.controllerStyle = style ;
		return this ;
	}

	addNameStyle(style){
		this.nameStyle = style ;
		return this ;
	}

	/**
	 * 设置当前文件夹下的所有controller的显示默认宽度
	 * @param width
	 * @returns {GUI}
	 */
	setNameWidth(width=100){
		if(width == null) return this;
		this.addNameStyle("--name-width:" + width + "px;");
		return this ;
	}

	addNameClass(clazz){
		this.nameClass = clazz ;
		return this ;
	}

	addGroup(group){
		this.group = group ;
		if(group != null)
			group.addFolder(this) ;
		return this ;
	}

	getUUID(){
		let s = [];
		let hexDigits = "0123456789abcdef";
		for (let i = 0; i < 36; i++) {
			s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
		}
		s[14] = "4";  // bits 12-15 of the time_hi_and_version field to 0010
		s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);  // bits 6-7 of the clock_seq_hi_and_reserved to 01
		s[8] = s[13] = s[18] = s[23] = "";
		let uuid = s.join("");
		return uuid ;
	}
}

GUI.onKeydown = null ;
GUI.onKeyup = null ;
